#include "common.h"
#include "fundamental.h"
#include "object.h"

#include "asset.h"
#include "mesh.h"
#include "effect.h"
#include "body.h"
#include "assetmanager.h"

#include "actormanager.h"
#include "actor.h"
#include "pawn.h"

#include "collada.h"

#include "colladawrapper.h"

using namespace ColladaWrapper;

static LPD3DVERTEXELEMENT9 ConvertVertElems( std::vector< Collada::VertexElement >& elems )
{
	size_t elemSize = elems.size()+1;
	LPD3DVERTEXELEMENT9 pDecl = new D3DVERTEXELEMENT9[ elemSize ];
	memset( pDecl, 0, sizeof( D3DVERTEXELEMENT9 )*elemSize );

	WORD offset = 0;
	for( size_t i = 0; i < elems.size(); ++i )
	{
		Collada::VertexElement elem = elems[i];
		BYTE type		= 0;
		WORD typeSize	= 0;
		BYTE semantic	= 0;
		BYTE index		= 0x000000FF & elem;

		switch( COLLADA_TYPE_MASK & elem )
		{
		case COLLADA_TYPE_FLOAT2 :
			type = D3DDECLTYPE_FLOAT2;
			typeSize = 8;
			break;
		case COLLADA_TYPE_FLOAT3 :
			typeSize = 12;
			type = D3DDECLTYPE_FLOAT3;
			break;
		}
		
		switch( COLLADA_SEMANTIC_MASK & elem )
		{
		case COLLADA_SEMANTIC_POSITION :
			semantic = D3DDECLUSAGE_POSITION;
			break;
		case COLLADA_SEMANTIC_NORMAL :
			semantic = D3DDECLUSAGE_NORMAL;
			break;
		case COLLADA_SEMANTIC_TEXCOORD :
			semantic = D3DDECLUSAGE_TEXCOORD;
			break;
		}
		
		pDecl[i].Offset			= offset;
		pDecl[i].Type			= type; 
		pDecl[i].Method			= D3DDECLMETHOD_DEFAULT;
		pDecl[i].Usage			= semantic; 
		pDecl[i].UsageIndex		= index; 

		offset += typeSize;
	}
	D3DVERTEXELEMENT9 end = D3DDECL_END();
	pDecl[ elems.size() ] = end;

	return pDecl;
}

static LPDIRECT3DVERTEXBUFFER9 ConvertVertBuf( Collada::Primitive& prim, Collada::Mesh& mesh )
{
	LPDIRECT3DDEVICE9 device = Core::GetInstance().GetD3dDevice();

	UINT unitCount = prim.indexCount;
	UINT unitSize = 0;
	std::vector< size_t > elemSize;
	for( size_t i = 0; i < prim.vertexElements.size(); ++i )
	{
		Collada::VertexElement elem = prim.vertexElements[i];
		
		switch( elem & COLLADA_TYPE_MASK )
		{
		case COLLADA_TYPE_FLOAT : 
			unitSize += 4;
			elemSize.push_back( 4 );
			break;
		case COLLADA_TYPE_FLOAT2 :
			unitSize += 8;
			elemSize.push_back( 8 );
			break;
		case COLLADA_TYPE_FLOAT3 :
			unitSize += 12;
			elemSize.push_back( 12 );
			break;
		}
	}

	LPDIRECT3DVERTEXBUFFER9 vb = NULL;
	if( FAILED( device->CreateVertexBuffer( 
			unitCount * unitSize, 
			0,
			NULL, 
			D3DPOOL_DEFAULT, 
			&vb, 
			NULL ) ) )
	{
		assert( false );
		return NULL;
	}

	BYTE* buffer = NULL;
	vb->Lock( 0, 0, ( void** )&buffer, 0 );

	UINT count = 0;
	while( count < ( unitCount / elemSize.size() ) )
	{
		int offset = count * unitSize;
		for( size_t i = 0; i < elemSize.size(); ++i )
		{
			WORD index = ( (short*)prim.indices )[ count*elemSize.size() + i ];
			BYTE* pSrc = ( (BYTE*)prim.buffers[i] ) + index*elemSize[i];

			memcpy( buffer+offset, pSrc, elemSize[i] );
			offset += elemSize[i];
		}
		++count;
	}

	vb->Unlock();
	return vb;
}

static D3DPRIMITIVETYPE ConvertPrimType( Collada::PrimitiveType type )
{
	switch( type )
	{
	case COLLADA_PT_LINES :			return D3DPT_LINELIST;
	case COLLADA_PT_LINESTRIPS :	return D3DPT_LINESTRIP;
	case COLLADA_PT_POLYGONS :		return D3DPT_TRIANGLELIST;
	case COLLADA_PT_POLYLIST :		return D3DPT_TRIANGLELIST;
	case COLLADA_PT_TRIANGLES :		return D3DPT_TRIANGLELIST;
	case COLLADA_PT_TRIFANS	:		return D3DPT_TRIANGLEFAN;
	case COLLADA_PT_TRISTRIPS :		return D3DPT_TRIANGLESTRIP;
	}

	return D3DPT_TRIANGLELIST;
}

static Mesh* ColladaMeshToMesh( Collada::Mesh& mesh, const std::string& meshName )
{
	const std::vector< Collada::Primitive* >& prims = mesh.GetMeshInfo();

	// meshName
	if( AssetManager::Find( meshName ) )
	{
		return AssetManager::GetMesh( meshName );
	}

	Mesh* pMesh = Mesh::Create( meshName );
	if( pMesh == NULL )
	{
		assert( false );
	}

	for( size_t i = 0; i < prims.size(); ++i )
	{
		// primitive type;
		D3DPRIMITIVETYPE type = ConvertPrimType( prims[i]->type );
		pMesh->SetPrimitiveType( type );

		// vertex element
		LPD3DVERTEXELEMENT9 elem = ConvertVertElems( prims[i]->vertexElements );
		LPDIRECT3DVERTEXDECLARATION9 decl;
		Core::GetInstance().GetD3dDevice()->CreateVertexDeclaration( elem, &decl );
		pMesh->SetVertexDeclaration( decl );
		delete elem;
		
		// vertex buffer
		LPDIRECT3DVERTEXBUFFER9 vb = ConvertVertBuf( *prims[i], mesh );
		pMesh->SetVertexBuffer( vb );

		// index buffer
		pMesh->SetIndexBuffer( NULL );
	}

	return pMesh;
}

void ColladaWrapper::GetBodyInfos( Body& body, Collada::Data* pData )
{
	Collada::VisualScene* pRoot = pData->GetScene()->GetRootScene();
	std::vector< Collada::Node* >& nodes = pRoot->GetNodeList();
	
	std::vector< Collada::Node* >::iterator it = nodes.begin();
	for( ; it != nodes.end(); ++it )
	{
		Body::Skin* pSkin = new Body::Skin;
		memset( pSkin, 0, sizeof( Body::Skin ) );

		Collada::Geometry* pGeom = pData->GetGeometry( ( *it )->GetURL() );
		const std::vector< Collada::Element* >& datas = pGeom->GetGeometricDatas();
		std::vector< Collada::Element* >::const_iterator cit = datas.begin();
		for( ; cit != datas.end(); ++cit )
		{
			// mesh
			if( ( *cit )->IsA( Collada::Mesh::GetTypeInfo() ) )
			{
				pSkin->mesh = AssetManager::GetMesh( pGeom->GetName() );
				body.AddSkin( pSkin );
			}

			// spline

			// brep
		}
	}	
}

void ColladaWrapper::RegisterAssets( Collada::Data* pData )
{
	Collada::VisualScene* pRoot = pData->GetScene()->GetRootScene();
	std::vector< Collada::Node* >& nodes = pRoot->GetNodeList();
	
	std::vector< Collada::Node* >::iterator it = nodes.begin();
	for( ; it != nodes.end(); ++it )
	{
		Collada::Geometry* pGeom = pData->GetGeometry( ( *it )->GetURL() );
		const std::vector< Collada::Element* >& datas = pGeom->GetGeometricDatas();
		std::vector< Collada::Element* >::const_iterator cit = datas.begin();
		for( ; cit != datas.end(); ++cit )
		{
			// convex mesh

			// mesh
			if( ( *cit )->IsA( Collada::Mesh::GetTypeInfo() ) )
			{
				Collada::Mesh& mesh = *( ( Collada::Mesh* )( *cit ) );
				Mesh* pMesh = ColladaMeshToMesh( mesh, pGeom->GetName() );
			}

			// spline

			// brep
		}
	}
}